// icosahedron.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "GL/glut.h"
#include "GL/glui.h"
#include "bfont.h"
#include <cmath>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

#define gR (1+sqrt(5.0f))/2	//golden ratio
#define X 1/sqrt(1+pow(gR,2)) 
#define Z gR/sqrt(1+pow(gR,2))

static GLfloat vdata[12][3] = {
{-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
{0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
{Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
};

static GLuint tindices[20][3] = {
{0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
{8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
{7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
{6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };

struct icoFc{
	float point1[3],point2[3], point3[3];
};

int fcCount = 0, curNumFc=0, depth = 1, tmpDepth =1;
struct icoFc ico[10000];
/*------------------------------global variables--------------------------------------*/
//transformation constants
#define TRANSFORM_NONE		0	 
#define TRANSFORM_ROTATE	1
#define TRANSFORM_SCALE	   	2 
#define TRANSFORM_TRANSLATE 3

//display mode constants
#define OBJ_WIREFRAME	0
#define OBJ_FLAT		1
#define OBJ_SMOOTH		2 
#define OBJ_POINT		3

//static variables used for menu
static int window;	
static int primitive = 0;

//Static variables used for transformation
static int press_x, press_y; 
static float x_angle = 0.0; 
static float y_angle = 0.0;  
static float scale_size = 1;
static float x_meas=0.0;
static float y_meas=0.0;

static int obj_mode = 0;
static int xtform_mode = 0; 



static float aspect_ratio = 1.0, centerX, centerY, centerZ;

//lighting variables
GLfloat	light_position[] = {1.0, 1.0, 10.0, 0.0 }, white_light[] = { 0.2, 0.5, 0.9, 1.0 }, lmodel_ambient[] = {0.9, 0.9, 0.9, 1.0 };

/** live variables passed into GLUI ***/
int bb = 0, projId = 0, fileId=0, bufFileId =0, grid = 0, xyz = 0; 

/*------------------------------global functions--------------------------------------*/
void center();				//calculate the center of model from vertices dataset.	
void drawIco();				//draw model file
void drawAxes();			//draw 3d axes

/*
void center()
{
    //compute the coordinates of the boundary
	for (int i = 0; i < (int)vertices.size(); i ++)
	{
		Vertex v = vertices[i];
		centerX+=v.x;
		centerY+=v.y;
		centerZ+=v.z;
	}
	centerX=centerX/vertices.size();
	centerY=centerY/vertices.size();
	centerZ=centerZ/vertices.size();
}
*/

void disp()
{
	glEnable(GL_DEPTH_TEST); 
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity(); 
	
	//setup perspective and orthographic Projection (simple, after reading 3dViewing_2008.pdf)
	if (projId==0)
		gluPerspective(60, aspect_ratio, 0.1,100);
    else
		glOrtho(-3*aspect_ratio, 3*aspect_ratio, -3*aspect_ratio, 3*aspect_ratio, 0.1,100 ); 

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity(); 
	gluLookAt(0,0,4,0,0,0,0,1,0); 

	//rotate and scale the object
	glRotatef(x_angle, 0, 1,0); 
	glRotatef(y_angle, 1,0,0); 
	glScalef(scale_size, scale_size, scale_size); 
	glTranslatef(x_meas,y_meas,0);

	glDisable(GL_LIGHTING); 
	glDisable(GL_LIGHT0);

	if(xyz == 1)drawAxes();	
	drawIco();
	
	//write the details of faces and subdivision depth
	string depth = "Depth: ", face = "# of faces: ";

	//combine int and string
	char numD[20], numF[50]; // enough to hold all numbers up to 64-bits
	sprintf(numD, "%d", tmpDepth);
	sprintf(numF, "%d", curNumFc);

	depth += numD;
	face +=numF;

	char* de;char* fc;
	de = new char[50]; 
	fc = new char[50];
	strcpy(de,depth.c_str());
	strcpy(fc,face.c_str());

	output(0, 20, de);
	output(0, 10, fc);
		
	glutSwapBuffers();
}

void myMouse(int button, int state, int x, int y)
{
	if (state == GLUT_DOWN) 
	{
		press_x = x; press_y = y; 
		if (button == GLUT_LEFT_BUTTON)
			xtform_mode = TRANSFORM_ROTATE; 
		else if (button == GLUT_MIDDLE_BUTTON) 
			xtform_mode = TRANSFORM_TRANSLATE; 
		else if (button == GLUT_RIGHT_BUTTON)
			xtform_mode = TRANSFORM_SCALE;
	}
	else if (state == GLUT_UP) 
	{
		xtform_mode = TRANSFORM_NONE; 
	}
}

void myTransform(int x, int y)
{
	if (xtform_mode == TRANSFORM_ROTATE) 
	{
		x_angle += (x - press_x)/5.0; 

		if (x_angle > 180) 
			x_angle -= 360; 
		else if (x_angle <-180) 
			x_angle += 360; 
      
		press_x = x; 
	   
		y_angle += (y - press_y)/5.0; 

		if (y_angle > 180) 
			y_angle -= 360; 
		else if (y_angle <-180) 
			y_angle += 360; 
      
		press_y = y; 
    }
	else if (xtform_mode == TRANSFORM_SCALE)
	{
		float old_size = scale_size;
		
		scale_size *= (1 + (y - press_y)/1000.0); 

		if (scale_size <0) 
			scale_size = old_size; 
		press_y = y; 
      }
	else if (xtform_mode == TRANSFORM_TRANSLATE)
     {
          x_meas -= (x-press_x)/500.0;
          press_x=x;
          y_meas += (y-press_y)/500.0;
          press_y=y;
     }    
	glutPostRedisplay(); 
}

void myKey(unsigned char key, int x, int y)
{
	switch(key) 
	{
    case '1':
         cout << "depth = 1" << endl;
         depth = 1;
         break;
	case '2':
         cout << "depth = 2" << endl;
         depth = 2;
         break;
	case '3':
         cout << "depth = 3" << endl;
         depth = 3;
         break;
    case '4':
         cout << "depth = 4" << endl;
         depth=4;
         break;
	case 'w': 
		cout << "key 'w' is pressed! draw the object in wireframe" << endl;
		obj_mode = OBJ_WIREFRAME;
		break; 
	case 'f':
		cout << "key 'f' is pressed! draw the object in solid" << endl;
		obj_mode = OBJ_FLAT;
		break;
	case 's':
		cout << "key 's' is pressed! draw the object in solid+wireframe" << endl;
		obj_mode = OBJ_SMOOTH;
		break;
    }
	// force the redraw function
	glutPostRedisplay(); 
}

void reshape(int w, int h)
{
	//Set the view port to entire window
	glViewport(0, 0, w, h);
	//Set the correct aspect ratio
	aspect_ratio = float(w)/float(h);
	glutPostRedisplay(); // force the redraw OpenGL (or GLUT)
}

void normalize(float v[3]) {
	GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
	v[0] /= d; v[1] /= d; v[2] /= d;
}

void subdivide(float *v1, float *v2, float *v3, long depth)
{
	GLfloat v12[3], v23[3], v31[3];
	GLint i;
	if (depth == 0) {
		for(i = 0; i< 3; i++)
		{
			ico[fcCount].point1[i] =v1[i];
			ico[fcCount].point2[i] =v2[i];
			ico[fcCount].point3[i] =v3[i];
		}
		fcCount++;
	return;
	}
	for (i = 0; i < 3; i++) {
	v12[i] = v1[i]+v2[i];
	v23[i] = v2[i]+v3[i];
	v31[i] = v3[i]+v1[i];
	}
	normalize(v12);
	normalize(v23);
	normalize(v31);
	subdivide(v1, v12, v31, depth-1);
	subdivide(v2, v23, v12, depth-1);
	subdivide(v3, v31, v23, depth-1);
	subdivide(v12, v23, v31, depth-1);
}

void drawIco()
{	
	for (int i = 0; i < 20; i++) 
	{
		subdivide(&vdata[tindices[i][0]][0],
		&vdata[tindices[i][1]][0],
		&vdata[tindices[i][2]][0],depth);
	}

	if (obj_mode == OBJ_WIREFRAME)
	{ 
	//draw the model in Wireframe view
	glDisable(GL_LIGHTING);
	glDisable(GL_LIGHT0);
	glColor3f(1.0,0,0);
		for(int i = 0; i<fcCount; i++)
		{
		glBegin(GL_LINES);
		glVertex3fv(ico[i].point1);
		glVertex3fv(ico[i].point2);
		glVertex3fv(ico[i].point2);
		glVertex3fv(ico[i].point3);
		glVertex3fv(ico[i].point3);
		glVertex3fv(ico[i].point1);
		glEnd();
		}
	}
	else if (obj_mode == OBJ_FLAT)
	{//draw the model in Flat-shaded view
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0); 			
	glShadeModel(GL_FLAT);
		for(int i = 0; i<fcCount; i++)
		{
		glBegin(GL_TRIANGLES);
		glNormal3fv(ico[i].point1);glVertex3fv(ico[i].point1);
		glNormal3fv(ico[i].point2);glVertex3fv(ico[i].point2);
		glNormal3fv(ico[i].point3);glVertex3fv(ico[i].point3);
		glEnd();
		}
	}
	else
	{ //draw the model in smooth-shaded view
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0); 			
	glShadeModel(GL_SMOOTH);
		for(int i = 0; i<fcCount; i++)
		{
		glBegin(GL_TRIANGLES);
		glNormal3fv(ico[i].point1);glVertex3fv(ico[i].point1);
		glNormal3fv(ico[i].point2);glVertex3fv(ico[i].point2);
		glNormal3fv(ico[i].point3);glVertex3fv(ico[i].point3);
		glEnd();
		}
	}
	
	if(tmpDepth != depth)
	{
		tmpDepth = depth;
	}
	if(curNumFc!= fcCount)
	{
		curNumFc = fcCount;
	}

	fcCount =0;
}

void drawAxes()
{
	glDisable(GL_LIGHTING); 
	glDisable(GL_LIGHT0);
	
	glLineWidth(5);
	glBegin(GL_LINES);
			// X coordinate axis
             glColor3f(1.0, 0.0, 0.0);
			 glVertex3f(centerX,centerY,centerZ);
			 glVertex3f(centerX+2,centerY,centerZ);

			 //Y coordinate axis
             glColor3f(0.0, 1.0, 0.0);
			 glVertex3f(centerX,centerY,centerZ);
			 glVertex3f(centerX,centerY+2,centerZ);

			 //Z coordinate axis
             glColor3f(0.0, 0.0, 1.0);
			 glVertex3f(centerX,centerY,centerZ);
			 glVertex3f(centerX,centerY,centerZ+2);

	glEnd();
	glLineWidth(1);
}


/*------------------------main function----------------------------------------*/
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE );
	glutInitWindowSize(900,900);
	glutInitWindowPosition(100,100);

	window = glutCreateWindow("icosahedron");
       
	//glClearColor(0.9,0.9,0.9,0.0);

	// register for callback functions
	glutDisplayFunc(disp);			//for display
	glutMouseFunc(myMouse);			//for mouse movements
	glutMotionFunc(myTransform);	//for motions
	glutKeyboardFunc(myKey);		//for keyboard
	glutReshapeFunc(reshape);		//for reshape
/*
	// if you'd rather to have separate window for UI use this GLUI Codes to initiate GLUI instead instead  
	GLUI *glui = GLUI_Master.create_glui("UI using GLUI",0,805,100);	

	GLUI *glui = GLUI_Master.create_glui_subwindow( window, 
					    GLUI_SUBWINDOW_BOTTOM );
	glui->add_checkbox("Axis XYZ",&xyz);
	glui->add_column(1);

	GLUI_Listbox *projBox=glui->add_listbox ( "Projection: ",&projId);
	projBox->add_item (0, "Perspective");
	projBox->add_item (1, "Orthographic");
	glui->add_column(1);

	glui->add_statictext("For camera control: ");
	glui->add_statictext("Rotate     -> Drag left mouse button");
	glui->add_statictext("Translate -> Drag middle mouse button");
	glui->add_statictext("Scale      -> Drag right mouse button");
	glui->add_separator();

	glui->add_statictext("For Display mode: ");
	glui->add_statictext("Point mode -> press 'p' on keyboard");
	glui->add_statictext("Wireframe   -> press 'w' on keyboard");
	glui->add_statictext("Flat           -> press 'f' on keyboard");
	glui->add_statictext("Smooth 	     -> press 's' on keyboard");

	glui->set_main_gfx_window( window );
*/
  	// enter the main loop
	glutMainLoop();
	//return 1;
}
/*------------------------end of main function----------------------------------------*/
